using System;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace Meta
{
	/// <summary>
	/// Summary description for DelegateGenerator.
	/// </summary>
	public class DelegateGenerator
	{
		static Hashtable WrapperTable = new Hashtable();
		public static Delegate AsDelegate(Type d, object arg) {
			if (d.IsInstanceOfType(arg)) 
				// It's already the right type
				return (Delegate)arg;
			else if (arg is Meta.Procedure) {
				Procedure p = (Procedure)arg;
				Type wrapperType = GetDelegateWrapperType(d);
				object wrapper = wrapperType.InvokeMember(null, BindingFlags.CreateInstance, null, null, new object[] { p });
				return Delegate.CreateDelegate(d, wrapper, "Invoke");
			} else
				throw new ArgumentException("Argument must be a procedure or delegate");
		}

		static Type GetDelegateWrapperType(Type d) {
			Type probe = WrapperTable[d] as Type;
			if (probe != null)
				return probe;

			Type t = MakeDelegateWrapperType(d);
			WrapperTable[d] = t;
			return t;
		}

		static Type MakeDelegateWrapperType(Type d) {
			string wName = d.Name+"Wrapper";
			AssemblyName n = new AssemblyName();
			n.Name = wName;
			AssemblyBuilder builder = Thread.GetDomain().DefineDynamicAssembly(n, AssemblyBuilderAccess.Run);
			ModuleBuilder module = builder.DefineDynamicModule(wName, true);
			TypeBuilder t = module.DefineType(wName);
			FieldInfo proc = t.DefineField("procedure", typeof(Meta.Procedure), FieldAttributes.Public | FieldAttributes.InitOnly);

			// Get the information about the delegate's type signature.
			// Surely there must be a better way to do this...
			MethodInfo dSignature = d.GetMethod("Invoke");

			// Constructor
			ConstructorBuilder c = t.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(Meta.Procedure) });
			ILGenerator cg = c.GetILGenerator();
			cg.Emit(OpCodes.Ldarg_0);
			cg.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));
			cg.Emit(OpCodes.Ldarg_0);
			cg.Emit(OpCodes.Ldarg_1);
			cg.Emit(OpCodes.Stfld, proc);
			cg.Emit(OpCodes.Ret);

			// Delegate trampoline
			ParameterInfo[] pars = dSignature.GetParameters();
			Type[] pTypes = new Type[pars.Length];
			Type[] metaArgTypes = new Type[pars.Length+1];
			metaArgTypes[0] = typeof(Procedure);
			for (int i=0; i<pars.Length; i++) {
				pTypes[i] = pars[i].ParameterType;
				metaArgTypes[i+1] = typeof(object);
			}
			
			MethodBuilder m = t.DefineMethod("Invoke", MethodAttributes.Public, dSignature.ReturnType, pTypes);
			ILGenerator mg = m.GetILGenerator();
			// Load the pointer to the Meta.Procedure
			mg.Emit(OpCodes.Ldarg_0);
			mg.Emit(OpCodes.Ldfld, proc);
			// Copy over the arguments, boxing them if necessary
			for (int j=0; j<pTypes.Length; j++) {
				mg.Emit(OpCodes.Ldarg, j+1);
				if (pTypes[j].IsSubclassOf(typeof(System.ValueType)))
					mg.Emit(OpCodes.Box, pTypes[j]);
			}
			// Find the appropriate call method to invoke on the Meta.Procedure and call it.
			MethodInfo callMeth = typeof(Meta.Procedure).GetMethod("TracedCall", metaArgTypes);
			mg.Emit(OpCodes.Call, callMeth);
			// Fix up the return value
			if (dSignature.ReturnType==typeof(void))
				mg.Emit(OpCodes.Pop);
			else if (dSignature.ReturnType.IsSubclassOf(typeof(System.ValueType)))
				mg.Emit(OpCodes.Unbox, dSignature.ReturnType);
			else if (dSignature.ReturnType!=typeof(object))
				mg.Emit(OpCodes.Castclass, dSignature.ReturnType);
			// else do nothing (because the return type is System.Object
			// Return
			mg.Emit(OpCodes.Ret);

			// All done.
			t.CreateType();
			return t;
		}
	}
}
